
// Copyright (C) 2010-2011 Luca Piccioni
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//   
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Schema;

using Derm.Xml;

namespace Derm.Render
{
	/// <summary>
	/// Any resource that is involved in the rendering operations.
	/// </summary>
	/// <remarks>
	/// <para>
	/// This class in the main implementation of the <see cref="IRenderResource"/> interface. This is the suggested implementation; in the case it
	/// is not possible to inherit class (i.e. a class is already inheriting from another one), it is possible to implement directly the
	/// <see cref="IRenderResource"/>. By implementing the <see cref="IRenderResource"/> interface without inheriting from RenderResource
	/// could leak some functionalities offered by RenderResource implementation. This subject will be discussed later, after the
	/// description of these functionalities.
	/// </para>
	/// <para>
	/// Most of the IRenderResource interface is implemented. The RenderResource inheritor shall implement the following methods:
	/// - <see cref="CreateName"/>: this method is abstract, and it shall generate an object name of a specific object class (determined by
	///   inheritor). It is called in the <see cref="IRenderResource.Create"/> implementation.
	/// - <see cref="CreateObject"/>: this method is virtual, and it can be overriden to actually create an useful object. Base implementation
	///   is empty. It is called in the <see cref="IRenderResource.Create"/> implementation, after <see cref="CreateName"/>.
	/// - <see cref="Delete"/>: this method is abstract, and it shall delete the object name and resources of this RenderResource instance.
	///   The implementation is dependent on the specific object class (determined by inheritor).
	/// - <see cref="Exists"/>: this method is virtual, but throw always <see cref="NotImplementedException"/>. Inheritor shall override this
	///   method since the implementation is dependent on the specific object class (determined by inheritor).
	/// </para>
	/// <para>
	/// This class defines also an invalid object name <see cref="InvalidObjectName"/>. Despite the name, this field doesn't actually specifies
	/// an invalid object name for all object classes. OpenGL specification usually uses this value for indicating an invalid object name, but it
	/// is not always the case (i.e. for framebuffer objects, InvalidObjectName indicates the default framebuffer object).
	/// 
	/// However, this name shall never be generated from <see cref="Create"/>.
	/// </para>
	/// <para>
	/// The RenderResource class implements the <see cref="IReferenced"/> interface. This allow additional checks for resource allocation and
	/// release. ...
	/// </para>
	/// <para>
	/// In order to aid debugging resource leaks, this class allow the object lifetime tracking. This functionality allow to track the following
	/// information:
	/// - The not yet disposed RenderResource instances.
	/// - The thread stack trace where each RenderResource instance was created.
	/// 
	/// This functionality is disabled by default, but is can be enabled by means of the property <see cref="LifetimeTracingEnabled"/>. When
	/// this flag is set to true, all RenderResource instances created later are tracked. The collected information about RenderResource instances is
	/// sufficient to determine resource leaks and locate them in the application code.
	/// 
	/// IRenderResource implementors (indeed the ones not inheriting from RenderResource) shall integrate this functionalities by the use of the static
	/// methods <see cref="TrackObjectLifetime"/> and <see cref="UntrackObjectLifetime"/>. The former method is tipically called at construction time,
	/// while the latter shall be called at disposition time (on <see cref="IDisposable.Dispose"/> implementation).
	/// </para>
	/// </remarks>
	public abstract class RenderResource : IRenderResource
	{
		#region Constructors

		/// <summary>
		/// Construct a RenderResource.
		/// </summary>
		protected RenderResource() : this(String.Empty)
		{
			
		}

		/// <summary>
		/// Construct a RenderResource, specifying its identifier.
		/// </summary>
		/// <param name="identifier">
		/// A <see cref="System.String"/> that identifies this RenderResource.
		/// </param>
		protected RenderResource(string identifier)
		{
			if (identifier == null)
				throw new ArgumentNullException("identifier");

			// Store identifier
			mIdentifier = identifier;

			// Lifetime tracking
			TrackObjectLifetime(this);
			// Track constructor stack trace
			if (sEnableLifetimeTracing == true) {
				// Store stack trace (keep track of created objects)
				mConstructorStackTrace = Environment.StackTrace;
			}
		}

		#endregion

		#region Lifetime Tracing

		/// <summary>
		/// Static flag indicating whether RenderResource instances lifetime tracing is enabled.
		/// </summary>
		/// <remarks>
		/// <para>
		/// When this flag is set to true, it enables RenderResource instances lifetime tracing. The lifetime tracing
		/// consist of:
		/// - Track the stack trace of the RenderResource constructor (1);
		/// - Track created and deleted objects (2);
		/// </para>
		/// </remarks>
		public static bool LifetimeTracingEnabled
		{
			get {
				return (sEnableLifetimeTracing);
			}
			set {
				sEnableLifetimeTracing = value;
			}
		}

		/// <summary>
		/// Track a RenderResource for lifetime information.
		/// </summary>
		/// <param name="rResource">
		/// A <see cref="IRenderResource"/> which has been constructed.
		/// </param>
		/// <remarks>
		/// This routine has a thread-safe implementation.
		/// </remarks>
		/// <exception cref="ArgumentNullException">
		/// Exception thrown if <paramref name="rResource"/> is null.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// Exception thrown if <paramref name="rResource"/> is already tracked for lifetime.
		/// </exception>
		internal static void TrackObjectLifetime(IRenderResource rResource)
		{
			if (rResource == null)
				throw new ArgumentNullException("rResource");

			if (sEnableLifetimeTracing == true) {
				// Collect object not disposed
				lock(sNotDisposedRenderObjectsLock) {
					if (sNotDisposedRenderObjects.Contains(rResource) == true)
						throw new ArgumentException("object already tracked for lifetime");
					sNotDisposedRenderObjects.Add(rResource);
				}
			}
		}

		/// <summary>
		/// Untrack a RenderResource for lifetime information.
		/// </summary>
		/// <param name="rResource">
		/// A <see cref="IRenderResource"/> which has been disposed.
		/// </param>
		/// <remarks>
		/// This routine has a thread-safe implementation.
		/// </remarks>
		/// <exception cref="ArgumentNullException">
		/// Exception thrown if <paramref name="rResource"/> is null.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// Exception thrown if <paramref name="rResource"/> is not tracked for lifetime.
		/// </exception>
		internal static void UntrackObjectLifetime(IRenderResource rResource)
		{
			if (rResource == null)
				throw new ArgumentNullException("rResource");

			if (sEnableLifetimeTracing == true) {
				// Remove object from existing object list
				lock (sNotDisposedRenderObjectsLock) {
					if (sNotDisposedRenderObjects.Contains(rResource) == false)
						throw new ArgumentException("object not tracked for lifetime");
					sNotDisposedRenderObjects.Remove(rResource);
				}
			}
		}

		#region Static Information

		/// <summary>
		/// Static flag indicating whether RenderResource lifetime tracing is enabled.
		/// </summary>
		private static bool sEnableLifetimeTracing;

		/// <summary>
		/// List of created, but not yet disposed objects (i.e. objects till existing).
		/// </summary>
		/// <remarks>
		/// This list can be used to track resource leaks.
		/// </remarks>
		private static readonly List<IRenderResource> sNotDisposedRenderObjects = new List<IRenderResource>();

		/// <summary>
		/// Thread safe access to <see cref="sNotDisposedRenderObjects"/>.
		/// </summary>
		private static readonly object sNotDisposedRenderObjectsLock = new object();

		#endregion

		#region Instance Information

		/// <summary>
		/// Stack trace 
		/// </summary>
		internal string ConstructorStackTrace { get { return (mConstructorStackTrace); } }

		/// <summary>
		/// String representing the stack trace of the constructor.
		/// </summary>
		private readonly string mConstructorStackTrace = "unspecified";

		#endregion

		#endregion

		#region Utilities

		/// <summary>
		/// Invalid object name.
		/// </summary>
		public const uint InvalidObjectName = 0;

		#endregion

		#region RenderResource Name Management

		/// <summary>
		/// Determine whether this object requires a name bound to a context or not.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for creating this object name.
		/// </param>
		/// <returns>
		/// It returns a boolean value indicating whether this RenderResource implementation requires a name
		/// generation on creation. In the case this routine returns true, the routine <see cref="CreateName"/>
		/// will be called (and it must be overriden). In  the case this routine returns false, the routine
		/// <see cref="CreateName"/> won't be called (and indeed it is not necessary to override it) and a
		/// name is generated automatically in a context-independent manner.
		/// </returns>
		protected virtual bool RequiresName(RenderContext ctx)
		{
			return (true);
		}

		/// <summary>
		/// Create a RenderResource name.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for creating this object name.
		/// </param>
		/// <returns>
		/// It returns a valid object name for this RenderResource.
		/// </returns>
		/// <exception cref="NotImplementedException">
		/// Exception always thrown.
		/// </exception>
		protected virtual uint CreateName(RenderContext ctx)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		/// Delete a RenderResource name.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for deleting this object name.
		/// </param>
		/// <param name="name">
		/// A <see cref="System.UInt32"/> that specifies the object name to delete.
		/// </param>
		/// <exception cref="NotImplementedException">
		/// Exception always thrown.
		/// </exception>
		protected virtual void DeleteName(RenderContext ctx, uint name)
		{
			throw new NotImplementedException();
		}

		#endregion

		#region Resource Management

		/// <summary>
		/// Actually create this RenderResource resources.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for allocating resources.
		/// </param>
		protected virtual void CreateObject(RenderContext ctx)
		{
			
		}

		#endregion

		#region IRenderResource Implementation

		/// <summary>
		/// Resource identifier.
		/// </summary>
		/// <remarks>
		/// This string is used to identify this RenderResource among a collection. This identifier has to be
		/// unique in order to be collected by <see cref="RenderResourceDb"/>.
		/// </remarks>
		public string Identifier
		{
			get { return (mIdentifier); }
			set {
				if (value == null)
					throw new ArgumentNullException("value");
				mIdentifier = value;
			}
		}

		/// <summary>
		/// Object class.
		/// </summary>
		/// <remarks>
		/// <para>
		/// The object class identify the resource type. It cannot be <see cref="System.Guid.Empty"/>, but a meaninfull
		/// value. The allowed values are determine in the concrete implementation of the IRenderResource
		/// implementation.
		/// </para>
		/// </remarks>
		public abstract Guid ObjectClass { get; }

		/// <summary>
		/// Object used for this RenderResource.
		/// </summary>
		public uint ObjectName
		{
			get { return (mObject); }
			protected set { mObject = value; }
		}

		/// <summary>
		/// Determine whether this RenderResource really exists.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> that would have created (or a sharing one) the object. This context shall be current.
		/// </param>
		/// <returns>
		/// It returns a boolean value indicating whether this object exists in the object space of <paramref name="ctx"/>. The current
		/// implementation actually does not test for object existence, but it tests for object name space correspondence.
		/// </returns>
		/// <exception cref="ArgumentNullException">
		/// Exception thrown if <paramref name="ctx"/> is null.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// Exception thrown if <paramref name="ctx"/> is not current to the calling thread.
		/// </exception>
		public virtual bool Exists(RenderContext ctx)
		{
			if (ctx == null)
				throw new ArgumentNullException("ctx");
			if (ctx.IsCurrent() == false)
				throw new ArgumentException("not current to this thread", "ctx");

			// Object not created
			if (ObjectName == InvalidObjectName)
				return (false);

			// Test only name space... specific test shall be executed by derived classes
			return (ctx.ObjectNameSpace == mObjectNameSpace);
		}

		/// <summary>
		/// Create this RenderResource.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for creating this object.
		/// </param>
		public virtual void Create(RenderContext ctx)
		{
			// Create object name
			if (RequiresName(ctx) == false) {
				lock (sClientObjectNamesLock) {
					// Ensure managed object class
					if (sClientObjectNames.ContainsKey(ObjectClass) == false)
						sClientObjectNames[ObjectClass] = 0;
					// Create name automatically
					mObject = ++sClientObjectNames[ObjectClass];
					// Store object name space to check on deletion
					if (ctx != null)
						mObjectNameSpace = ctx.ObjectNameSpace;
				}
			} else {
				if (ctx == null)
					throw new ArgumentException("ctx");
				if (ctx.IsCurrent() == false)
					throw new ArgumentException("not current to this thread", "ctx");
				if (mObject != InvalidObjectName)
					throw new InvalidOperationException("object name leak");

				// Create a name for this resource
				mObject = CreateName(ctx);
				// Store object name space to check on deletion
				mObjectNameSpace = ctx.ObjectNameSpace;
			}
			// Create object
			CreateObject(ctx);
		}

		/// <summary>
		/// Delete this RenderResource.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for deleting this object.
		/// </param>
		public virtual void Delete(RenderContext ctx)
		{
			// Delete object name
			if (RequiresName(ctx)) {
				if (ctx == null)
					throw new ArgumentException("ctx");
				if (ctx.IsCurrent() == false)
					throw new ArgumentException("not current to this thread", "ctx");
				if (ctx.ObjectNameSpace != mObjectNameSpace)
					throw new ArgumentException("context name space mismatch", "ctx");

				// Delete this resource
				DeleteName(ctx, mObject);
			}
			// Reset object name space
			mObjectNameSpace = Guid.Empty;
			// Ensure non valid object name
			mObject = InvalidObjectName;
		}

		/// <summary>
		/// Release this RenderResource.
		/// </summary>
		/// <remarks>
		/// The implementation of this method shall release the graphics resources, not the object itself. This won't actually release anything: it collects this instance into
		/// the RenderResource garbage collector. For more information see <see cref="RenderGarbageService"/>.
		/// </remarks>
		/// <exception cref="InvalidOperationException">
		/// This exception is thrown if <see cref="RefCount"/> is greater than 0. This usually means that another resource referencing this RenderResource
		/// requires this object.
		/// </exception>
		public void Release()
		{
			if (RefCount > 0)
				throw new InvalidOperationException("RefCount greater than 0");

			if (ObjectName != InvalidObjectName) {
				RenderGarbageService rGarbageService = RenderGarbageService.GetService(mObjectNameSpace);
				
				if (rGarbageService == null)
					throw new InvalidOperationException("no garbage service");

				rGarbageService.CollectGarbage(this);
			}
		}

		/// <summary>
		/// RenderResource identifier.
		/// </summary>
		private string mIdentifier;

		/// <summary>
		/// RenderResource name.
		/// </summary>
		/// <remarks>
		/// A value of 0 indicates that this RenderResource name is not yet created.
		/// </remarks>
		private uint mObject = InvalidObjectName;

		/// <summary>
		/// RenderResource name space.
		/// </summary>
		/// <remarks>
		/// A value of <see cref="System.Guid.Empty"/> indicates that this RenderResource doesn't belong to
		/// any context name space (i.e. its name is not yet created).
		/// </remarks>
		private Guid mObjectNameSpace = Guid.Empty;

		/// <summary>
		/// Common name database for objects not supported by OpenGL context.
		/// </summary>
		private static readonly Dictionary<Guid, uint> sClientObjectNames = new Dictionary<Guid, uint>();

		/// <summary>
		/// Lock for accessing <see cref="sClientObjectNames"/>.
		/// </summary>
		private static readonly object sClientObjectNamesLock = new object();

		#endregion

		#region IXmlSerializableExt Implementation

		/// <summary>
		/// This method is reserved (instead use System.Xml.Serialization.XmlSchemaProviderAttribute to the class instead).
		/// </summary>
		/// <returns>
		/// It always returns null.
		/// </returns>
		public XmlSchema GetSchema() { return (null); }

		/// <summary>
		/// Generates an object from its XML representation.
		/// </summary>
		/// <param name="xmlReader">
		/// The System.Xml.XmlReader stream from which the object is deserialized.
		/// </param>
		public void ReadXml(XmlReader xmlReader)
		{
			XmlHelper.ReadXml(xmlReader, this);
		}

		/// <summary>
		/// Converts an object into its XML representation.
		/// </summary>
		/// <param name="writer">
		/// The System.Xml.XmlWriter stream to which the object is serialized.
		/// </param>
		public void WriteXml(XmlWriter writer)
		{
			
		}

		/// <summary>
		/// Read the attribute of the current XML serializable.
		/// </summary>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for reading element attributes.
		/// </param>
		public virtual void ReadAttributes(XmlReader xmlReader)
		{
			if (xmlReader == null)
				throw new ArgumentNullException("xmlReader");

			// Check required attributes
			if (xmlReader[XmlNameIdentifier] == null)
				throw new XmlException(String.Format("required attribute {0} not specified", XmlNameIdentifier));

			// Identifier
			mIdentifier = xmlReader[XmlNameIdentifier];
		}

		/// <summary>
		/// Read an element of the current XML serializable.
		/// </summary>
		/// <param name="elementName">
		/// A <see cref="System.String"/> that specify the element name.
		/// </param>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for reading child element.
		/// </param>
		public virtual void ReadElement(string elementName, XmlReader xmlReader)
		{
			if (elementName == null)
				throw new ArgumentNullException("elementName");
			if (xmlReader == null)
				throw new ArgumentNullException("xmlReader");

			// Skip all element children
			xmlReader.Skip();
		}

		/// <summary>
		/// Read the text of the current XML serializable.
		/// </summary>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for reading child element. It is positioned at the start element.
		/// </param>
		public void ReadText(XmlReader xmlReader)
		{
			
		}

		/// <summary>
		/// Name of the 'sXmlNameCurrentView' attribute that specify the currently selected view identifier.
		/// </summary>
		private const string XmlNameIdentifier = "Identifier";

		#endregion

		#region IReferenced Implementation

		/// <summary>
		/// Increase the reference count of this RenderResource.
		/// </summary>
		public void Ref()
		{
			mRefs++;
		}

		/// <summary>
		/// Decrease the reference count of this RenderResource.
		/// </summary>
		/// <exception cref="InvalidOperationException">
		/// This exception is thrown if <see cref="RefCount"/> is 0.
		/// </exception>
		public void Unref()
		{
			if (mRefs == 0)
				throw new InvalidOperationException("reference count is 0");

			// Decrease reference count
			mRefs--;
			// Dispose this object, if it no other one is referencing it.
			if (mRefs == 0)
				Dispose();
		}

		/// <summary>
		/// Reference count for this RenderResource.
		/// </summary>
		public uint RefCount { get { return (mRefs); } }

		/// <summary>
		/// Reference count for this RenderResource.
		/// </summary>
		private uint mRefs;

		#endregion

		#region IDisposable Implementation

		/// <summary>
		/// Finalizer.
		/// </summary>
		~RenderResource()
		{
			Dispose(false);
		}

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or resetting managed/unmanaged resources.
		/// </summary>
		/// <exception cref="InvalidOperationException">
		/// Exception thrown in the case <see cref="RenderResource.RefCount"/> is greater than zero. This means that the method is trying to dispose
		/// an object that is actually referenced by something else.
		/// </exception>
		protected virtual void Dispose(bool disposing)
		{
			if (RefCount > 0)
				throw new InvalidOperationException(String.Format("disposing already referenced {0} constructed at {1}", GetType(), ConstructorStackTrace));

			if (disposing == true) {
				// Release resources allocated by this RenderResource
				Release();
				// Life time tracking
				UntrackObjectLifetime(this);
			}
		}

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		/// </summary>
		/// <exception cref="InvalidOperationException">
		/// Exception thrown in the case <see cref="RefCount"/> is greater than zero. This means that the method is trying to dispose
		/// an object that is actually referenced by something else.
		/// </exception>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		#endregion
	}
}
